package p47_autowired注入原理;

import lombok.ToString;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Optional;

/**
 * autowired注解(这节也是第四章的延续)
 * 前面讲过，解析@Autowire的bean后处理器会将目标属性封装成DependencyDescriptor，然后用DependencyDescriptor去容器中找对应的bean，找到就赋值给目标属性
 * 但实际开发中，@Autowire修饰的类型可能很丰富，有普通bean，有集合类型，有嵌套类型(Optional),有ObjectFactory和ObjectProvider类型或者配合@Lazy的代理类型
 * 下面演示了遇到这几种情况，@Autowire后处理器内部会怎么处理
 */
@Configuration
public class _Autowired {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(_Autowired.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();

        /**
         * 下面演示几种@Autowired在不同情况下，bean后处理器是如何解析的(演示如何调用api)
         * 为了简单，这里将要找到并注入的bean仅仅打印出来，不赋值给目标对象的属性了
         */
        //1.根据属性变量的类型，从容器中找到bean
        DependencyDescriptor dd1 = new DependencyDescriptor(Bean1.class.getDeclaredField("bean2"), false);
        System.out.println("根据属性类型找到bean=>" + beanFactory.doResolveDependency(dd1, "otherBean", null, null));
        //2.根据参数类型，找到bean，需要方法对象和方法参数的下标
        Method setBean = Bean1.class.getDeclaredMethod("setBean", Bean2.class);
        DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean, 0), false);
        System.out.println(("根据参数类型找到bean=>" + beanFactory.doResolveDependency(dd2, "otherBean", null, null)));
        //3.结果包装为Optional，需要跑到内层得到真实类型，然后按照类型从容器中找到bean，将该bean封装成Optional
        DependencyDescriptor dd3 = new DependencyDescriptor((Bean1.class.getDeclaredField("optional")), false);
        if (dd3.getDependencyType() == Optional.class) {
            //跑到内层得到内层类型
            dd3.increaseNestingLevel();
            Object result = beanFactory.doResolveDependency(dd3, "otherBean", null, null);
            Optional<Object> op = Optional.ofNullable(result);
            System.out.println(("嵌套类型找到bean=>" + op));
        }

        //4.结果包装为ObjectFactory(ObjectProvider同理)。这种推迟了bean的注入
        DependencyDescriptor dd4 = new DependencyDescriptor((Bean1.class.getDeclaredField("factory")), false);
        if (dd4.getDependencyType() == ObjectFactory.class) {
            dd4.increaseNestingLevel();

            //调用该对象的getObject方法，才会去容器中找到bean，并进行注入，推迟了注入过程
            ObjectFactory factory = new ObjectFactory() {
                //调用这个方法，才去容器里找bean
                @Override
                public Object getObject() throws BeansException {
                    Object result = beanFactory.doResolveDependency(dd4, "otherBean", null, null);
                    return result;
                }
            };
            System.out.println("通过ObjectFactory延迟获得bean=>" + factory.getObject());

            //5.@Lazy配合@Autowired使用，找到代理bean，而不是目标bean注入
            DependencyDescriptor dd5 = new DependencyDescriptor((Bean1.class.getDeclaredField("otherBean")), false);
            //ContextAnnotationAutowireCandidateResolver不仅能解析@Value，还能解析@Lazy。发现某个@Autowired变量加了@Lazy，就自动创建一个代理并返回
            //注入的就是这个代理，这个代理对象在将来被访问时，返回目标bean，从而实现延迟注入
            ContextAnnotationAutowireCandidateResolver resolver = new ContextAnnotationAutowireCandidateResolver();
            resolver.setBeanFactory(beanFactory);
            Object proxy = resolver.getLazyResolutionProxyIfNecessary(dd5, "otherBean"); //发现有Lazy注解就调用这个方法创建一个代理
            System.out.println("通过@Lazy得到代理bean=>" + proxy);
            System.out.println("通过@Lazy得到代理bean类型=>" + proxy.getClass());
        }

    }

    @Component
    class Bean1 {
        @Autowired
        private Bean2 bean2;

        @Autowired
        public void setBean(Bean2 bean2) {
            System.out.println(bean2);
        }

        @Autowired
        private Optional<Bean2> optional;

        @Autowired
        private ObjectFactory<Bean2> factory;

        @Autowired
        @Lazy
        private Bean2 otherBean;

    }

    @Component("otherBean")
    @ToString
    class Bean2 {

    }
}
